package org.piax.rospubsub;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

import org.piax.rospubsub.ADBBridge.ADBBridgeCallback;

/**
 * ADBBridge 接続受け付けクラス
 */
public class ServerADBBridge {
    private ServerSocketChannel ssoc = null;
    private volatile boolean threadLoop = false;
    private Thread acceptThread = null;

    private AcceptCallback acceptCallback = null;
    private ADBBridgeCallback recvCallback = null;
    private int bufSize = -1;

    /**
     * 接続を受け付けた場合に呼ばれる callback
     */
    public interface AcceptCallback {
        /**
         * 接続を受け付けた場合に呼ばれる callback
         * @param bridge 接続された ADBBridge
         */
        void onAccept(ADBBridge bridge);
    }

    /**
     * コンストラクタ
     * 
     * @param endpoint 接続待ち受けendpoint
     * @param acceptcallback 接続を受け付けた場合に呼ばれる callback
     * @param recvcallback 接続を受け付け後に作られる ADBBridge に与える ADBBridgeCallback
     * @param buf_size 接続を受け付け後に作られる ADBBridge に与える送受信バッファサイズ このバッファサイズがメッセージサイズの上限
     * @throws IOException 待ち受け開始時に例外が生じた場合
     */
    public ServerADBBridge(InetSocketAddress endpoint, AcceptCallback acceptcallback, ADBBridgeCallback recvcallback, int buf_size) throws IOException {
        if (endpoint == null)
            throw new IllegalArgumentException("endpoint should not be null");
        if (acceptcallback == null)
            throw new IllegalArgumentException("acceptcallback should not be null");
        if (recvcallback == null)
            throw new IllegalArgumentException("recvcallback should not be null");
        if (buf_size < 7)
            throw new IllegalArgumentException("buf_size should be greater than 6");

        this.acceptCallback = acceptcallback;
        this.recvCallback = recvcallback;
        this.bufSize = buf_size;

        threadLoop = true;
        ssoc = ServerSocketChannel.open();
        ssoc.socket().bind(endpoint);
        acceptThread = new AcceptThread();
        acceptThread.start();
    }

    /**
     * 接続待ち受けを停止する。
     * @throws IOException
     * @throws InterruptedException
     */
    public void close() throws IOException, InterruptedException {
        if (!threadLoop)
            return;

        threadLoop = false;
        ssoc.close();
        acceptThread.interrupt();
        acceptThread.join();
    }

    /**
     *  接続受け付けスレッド。
     */
    private class AcceptThread extends Thread {
        @Override
        public void run() {
            while (threadLoop) {
                try {
                    SocketChannel soc = ssoc.accept();
                    acceptCallback.onAccept(new ADBBridge(soc, recvCallback, bufSize));
                } catch (IOException e) {
                    if (threadLoop) {
                        // threadLoop == true で IOException は切断などの例外事項
                        e.printStackTrace();
                        threadLoop = false;
                    }
                    // threadLoop == false での IOException は正常停止時
                }
            }
        }
    }
}
